use actix::prelude::*;
use chrono::Local;
use chrono::prelude::*;
use serde_json::json;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs;
use std::sync::Arc;
use std::sync::Mutex;
use crate::LOCAL_CONFIG;
use crate::config::ConfigAction;
use crate::kv::KVStore;
use crate::{session, SessionStore, kv, DbPool, actions, models};

use tokio::net::UnixStream;
use tonic::transport::{Endpoint, Uri};
use tower::service_fn;
use tokio_stream::{self as stream, StreamExt};
use tokio::io::Interest;
use tokio::net::UnixDatagram;
use std::io;
use crate::models::{Rule, YaraResult};
use crate::model;

// Define actor
pub struct VirusScheduler {
    db: kv::RocksDB,
    pool: DbPool,
    last_task: Arc<Mutex<YaraResult>>,
}

// Provide Actor implementation for our actor
impl Actor for VirusScheduler {
    type Context = Context<Self>;

    fn started(&mut self, _ctx: &mut Self::Context) {
        println!("VirusScheduler Actor is started");
        let arbiter = Arbiter::new();
        arbiter.spawn(VirusScheduler::execute(self.db.clone(), self.pool.clone(), self.last_task.clone()));
    }

    fn stopped(&mut self, _ctx: &mut Context<Self>) {
        println!("VirusScheduler Actor is stopped");
    }
}

impl std::fmt::Display for models::MalObjType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl VirusScheduler {

    pub(crate) fn get_scheduler(db: kv::RocksDB, pool: DbPool, last_task: Arc<Mutex<YaraResult>>) -> Self {
        VirusScheduler { db, pool, last_task  }
    }

    async fn execute(db: kv::RocksDB, pool: DbPool, last_task: Arc<Mutex<YaraResult>>) {

        let conn = pool.get().unwrap();

        let client_path = "/var/run/yara.sock";
        let _ = fs::remove_file(client_path);
        let socket = UnixDatagram::bind(&client_path).unwrap();
        let mut data = [0; 1024 * 1024];
        loop {
            let ready = socket.ready(Interest::READABLE).await.unwrap();
            if ready.is_readable() {
                match socket.try_recv(&mut data[..]) {
                    Ok(n) => {
                        
                        // 原始数据
                        let doc_json = String::from_utf8_lossy(&data[..n]).to_string();

                        let value: YaraResult = serde_json::from_str(doc_json.as_str()).unwrap();
                        
                        // TODO 进度缓存
                        println!("scan {}/{}", value.task_total, value.task_finish);
                        
                        let mut data = last_task.lock().unwrap();
                        data.matches = value.matches;
                        data.mal_obj_type = value.mal_obj_type.clone();
                        data.mal_obj_key = value.mal_obj_key.clone();
                        data.task_total = value.task_total;
                        data.task_finish = value.task_finish;
                        data.task_match = value.task_match;

                        if value.matches {
                            match value.rule_id {
                                Some(rule_ids) => {
                                    for rule_id in rule_ids {
                                        let key = Local::now().format("%Y%m%d%H%M%S%f").to_string();

                                        println!("VirusScheduler socket.try_recv {} {}", key, rule_id);
            
                                        let timestamp = value.timestamp;
                                        
                                        // save to rocksdb
                                        db.save(&key, &doc_json);
            
                                        let lang = match LOCAL_CONFIG.get("lang") {
                                            Some(v) => v.to_string(),
                                            None => "zh-CN".to_string(),
                                        };
                                        let rule_key = &format!("yara-{}", rule_id);
            
                                        
                                        let mut rule_value = String::from("");
                                        while rule_value == "" {
                                            match db.find(rule_key) {
                                                Some(r) => {
                                                    rule_value = r;
                                                    break;
                                                },
                                                None => {
                                                    std::thread::sleep(std::time::Duration::from_millis(1000));
                                                },
                                            }
                                        }
            
                                        let rule_with_lang: HashMap<String, model::rule_meta_model::RuleMeta> = serde_json::from_str(&rule_value).unwrap();
            
                                        let rule = rule_with_lang.get(&lang).unwrap();
                                        // 
                                        // let mal_obj = json!([{
                                        //     "mal_obj_type": value.mal_obj_type,
                                        //     "mal_obj_key": value.mal_obj_key,
                                        // }]);
                                        // let mal_obj_json = serde_json::to_string(&mal_obj).unwrap();
                                        
                                        // save to sqlite
                                        let _db_result = actions::insert_new_alert(models::Alert {
                                            id: key,
                                            severity: rule.severity.clone(),
                                            category: rule.category.clone(),
                                            description: rule.name.clone(),
                                            time: timestamp,
                                            source: "病毒扫描".to_string(),
                                            mal_obj_type: value.mal_obj_type.to_string(),
                                            mal_obj_key: value.mal_obj_key.to_string(),
                                            mal_obj_country: "".to_string(),
                                            mal_obj_city: "".to_string(),
                                            rule_id: rule_id.to_string(),
                                            solved: false,
                                        }, &conn);
            
                                        //println!("{:?}", db_result);
                                    }
                                },
                                None => (),
                            }
                                                     
                        }
                        
                    }
                    // False-positive, continue
                    Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
                    Err(e) => {
                        println!("error {:?}", e);
                        break;
                    }
                }
            }
        }
    }
}